
#include <ESP8266WiFi.h>
#include <DNSServer.h>
#include <ESP8266WebServer.h>
#include <SoftwareSerial.h>
#include <Ticker.h>
#include <Servo.h>
#include <EEPROM.h>

//-------------------------------- 关于按键的一些定义-----------------------------
#define PLUS D1         // +
#define SUB  D2         // -

//读按键值
#define KEYP      digitalRead(PLUS)
#define KEYS      digitalRead(SUB)

#define short_press_time 10     //短按时间
#define long_press_time 1000    //长按时间

#define NO_KEY_PRES       0   //无按键按下
 
#define PLUS_KEY_PRES     1
#define SUB_KEY_PRES      2
 
#define PLUS_KEY_LONG_PRES     11
#define SUB_KEY_LONG_PRES      22

//-------------------------------  页面代码  ----------------------------------------
// 首页
const char* page_html = "\
<!DOCTYPE html>\r\n\
<html lang='en'>\r\n\
<head>\r\n\
  <meta charset='UTF-8'>\r\n\
  <meta name='viewport' content='width=device-width, initial-scale=1.0'>\r\n\
  <title>Switch</title>\r\n\
  <style>\r\n\
    body {\r\n\
      background-color: rgb(247, 250, 95);\r\n\
    }\r\n\
    .box {\r\n\
      width: 200px;\r\n\
      height: 110px;\r\n\
      background-color: rgb(92, 134, 250);\r\n\
      border: 4px solid gray;\r\n\
      position: absolute;\r\n\
      left: 50%;\r\n\
      top: 50%;\r\n\
      margin-top: -55px;\r\n\
      margin-left: -100px;\r\n\
      border-radius: 10px;\r\n\
      font-size: 70px;\r\n\
      font-weight: 500;\r\n\
      color: white;\r\n\
      outline: none;\r\n\
    }\r\n\
  </style>\r\n\
</head>\r\n\
<body>\r\n\
  <form name='input' action='/SWITCH' method='POST' >\r\n\
    <input type='submit' value='开/关' class='box'><br>\r\n\
  </form>\r\n\
</body>\r\n\
</html>\r\n\
";
// 操作成功页
const char* page_success_html = "\
<!DOCTYPE html>\r\n\
<html lang='en'>\r\n\
<head>\r\n\
  <meta charset='UTF-8'>\r\n\
  <meta name='viewport' content='width=device-width, initial-scale=1.0'>\r\n\
  <title>Switch</title>\r\n\
  <style>\r\n\
    body {\r\n\
      background-color: rgb(247, 250, 95);\r\n\
    }\r\n\
    .box {\r\n\
      width: 200px;\r\n\
      height: 110px;\r\n\
      background-color: rgb(92, 134, 250);\r\n\
      border: 4px solid gray;\r\n\
      position: absolute;\r\n\
      left: 50%;\r\n\
      top: 50%;\r\n\
      margin-top: -55px;\r\n\
      margin-left: -100px;\r\n\
      border-radius: 10px;\r\n\
      font-size: 40px;\r\n\
      font-weight: 400;\r\n\
      color: white;\r\n\
      outline: none;\r\n\
    }\r\n\
  </style>\r\n\
</head>\r\n\
<body>\r\n\
  <form name='input' action='/SUCCESS' method='POST' >\r\n\
    <input type='submit' value='操作成功' class='box'><br>\r\n\
  </form>\r\n\
</body>\r\n\
</html>\r\n\
";

//-------------------------------  环境配置  ----------------------------------------
const char* AP_SSID = "Switch";    // 这里为AP名字--AP账号
const char* AP_PSW = "12345678";   // 这里为AP密码 8位以上
const byte DNS_PORT = 53;          // DNS端口号

bool ledBlinkFlag = 0;             // 低电压LED闪烁标志位
bool switchStatus = 0;             // 当前开关转态，默认为0

bool KEYP_UP = 0;                  //按键状态，0为弹起，1为按下
bool KEYS_UP = 0;                  //按键状态，0为弹起，1为按下

uint16_t KEYP_PRESS_COUNT = 0;     //按KEYU时间计数
uint16_t KEYS_PRESS_COUNT = 0;     //按KEYM时间计数

uint8_t KEYP_KEY_READ = NO_KEY_PRES;//KEYU按键状态
uint8_t KEYS_KEY_READ = NO_KEY_PRES;//KEYM按键状态

bool PLUS_INIT = 0;                 //舵机角度调整，0为不工作，1为工作
bool SUB_INIT = 0;                  //舵机角度调整，0为不工作，1为工作
bool ACK = 1;                       //舵机角度确定，0为确定，1为不确定

IPAddress apIP(192, 168, 4, 1);    // esp8266-AP-IP地址
DNSServer dnsServer;               // 创建dnsServer实例
ESP8266WebServer server(80);       // 创建WebServer
SoftwareSerial btSerial(D7, D8);   // 串口2: Rx,Tx
Servo myServo;                     // 定义Servo对象
Ticker adcTick;                    // 电压检测定时器
Ticker ledTick;                    // LED闪烁定时器
Ticker keyTick;                    // 按键检测定时器

byte byte1=0;
int pos = 90;                      // 角度存储变量
int posChange = 0;                 // 改变的角度值

//-------------------------------  网页请求处理  ----------------------------------------
void handleRoot() {//访问主页回调函数
  server.send(200, "text/html", page_html);
}

//Post回调函数:处理LED控制请求
void handleRootPost() {
  //digitalWrite(D5,!digitalRead(D5));// 改变LED的点亮或者熄灭状态
  servoService();
  //server.sendHeader("Location",page_success_html);// 跳转回页面根目录
  server.send(200, "text/html", page_success_html);
  server.send(303);                 // 发送Http相应代码303 跳转  
  Serial.println("/SWITCH 请求");
}

void handleRootSuccess() {//访问主页回调函数
  server.send(200, "text/html", page_html);
  Serial.println("/SUCCESS请求");
}

//-------------------------------  配置函数  ----------------------------------------
//基础初始化
void initBasic(void){
  delay(1000);
  Serial.begin(115200);   // 串口波特率
  btSerial.begin(9600);   // 蓝牙波特率 
  EEPROM.begin(1024);     // 开启EEPROM，开辟1024个位空间
  EEPROM.write(0,byte1);              //给EEPROM 第0位，写入byte_1的值
  EEPROM.commit();
  
  pinMode(D5, OUTPUT);    // 绿灯
  pinMode(D3, OUTPUT);    // 黄灯
  Serial.println("Started...");
  btSerial.write("start");
  
  myServo.attach(12);     // D6 
  myServo.write(pos);     // 舵机角度写入
  delay(50);
  
  //prepare interupt  设置外置按钮管脚为上拉输入模式
  pinMode(PLUS,INPUT_PULLUP);
  pinMode(SUB,INPUT_PULLUP);

  digitalWrite(D5,LOW);     // 绿灯熄灭
  ledBlinkFlag = 1;         // 黄灯闪烁

  
  adcTick.attach(5, adcService);   //电压检测定时器（每5秒检测一次）
  ledTick.attach_ms(250, ledBlink);//led闪烁定时器（每250毫秒闪烁一次）
  keyTick.attach_ms(1, flip);      //按键检测定时器（每1毫秒检测一次）

  int eeprom_pos=EEPROM.read(0);
  Serial.print("内存中的角度值eeprom_pos=");
  Serial.println(eeprom_pos);
  pos=eeprom_pos;
  posChange = abs(pos-90);
  // 舵机初始化,rom中有值就无需进行舵机初始化
  if(eeprom_pos<60||eeprom_pos>130){
    pos=90;
    initServo();
  }
}

//初始化AP模式
void initSoftAP(void){
  WiFi.mode(WIFI_AP);
  WiFi.softAPConfig(apIP, apIP, IPAddress(255, 255, 255, 0));
  if(WiFi.softAP(AP_SSID,AP_PSW)){
    Serial.println("ESP8266 SoftAP is right");
  }
}

//初始化WebServer
void initWebServer(void){
  /*必须在第二个参数上添加上HTTP_GET才能不影响强制门户，防止有些设备无法弹出强制门户，要用域名访问,
    如果不加第二个参数，就只能实现域名访问而无法强制门户
    在无法响应的http请求响应回调设置为主页的回调函数，才可以强制门户*/
  server.on("/", HTTP_GET, handleRoot);//设置主页回调函数
  server.onNotFound(handleRoot);//设置无法响应的http请求的回调函数
  server.on("/SWITCH", HTTP_POST, handleRootPost);//设置Post请求回调函数
  server.on("/SUCCESS", HTTP_POST, handleRootSuccess);//设置Post请求回调函数
  server.begin();//启动WebServer
  Serial.println("WebServer started!");
}

//初始化DNS
void initDNS(void){//初始化DNS服务器
  if(dnsServer.start(DNS_PORT, "*", apIP)){//判断将所有地址映射到esp8266的ip上是否成功
    Serial.println("start dnsserver success.");
  }
  else Serial.println("start dnsserver failed.");
}

//舵机角度初始化
void initServo(){
  Serial.println("舵机角度初始化");
  while(ACK){
    if(PLUS_INIT){
      pos += 10;
      PLUS_INIT = 0;
    }
    if(SUB_INIT){
      pos -= 10;
      SUB_INIT = 0;
    }
    myServo.write(pos);              // 舵机角度写入
    delay(100);
  }
  //读取与保存byte类型
  EEPROM.write(0,pos);             //给EEPROM 第0位，写入byte_1的值
  EEPROM.commit();
  int eeprom_pos=EEPROM.read(0);
  digitalWrite(D5,HIGH);             // 绿灯亮
  Serial.println("舵机角度调整结束");
  Serial.println(pos);
  Serial.print("新写入的角度new_eeprom_pos=");
  Serial.println(eeprom_pos);
  posChange = abs(pos-90);
  myServo.write(90);                 // 舵机角度写入
  delay(100);
}

//WiFi环境初始化
void connectNewWifi(void){
  WiFi.setAutoConnect(true);  //设置自动连接(经过测试，强制门户必须要加上这一句)
  //WiFi.begin();             //(经过测试，强制门户必须要加上这一句，我并没有找到原因)
  int count = 0;
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    count++;
    if(count > 10){//如果5秒内没有连上，就开启Web配网 可适当调整这个时间
      initSoftAP();
      initWebServer();
      initDNS();
      break;//跳出 防止无限初始化
    }
    Serial.print(".");
  }
  ledBlinkFlag = 0;         // 初始化结束，黄灯停止闪烁
  digitalWrite(D3,LOW);     // 黄灯灭
  digitalWrite(D5,HIGH);    // 绿灯亮
}

//-------------------------------  服务函数  ----------------------------------------
//蓝牙服务
void bluetoothService(){
  if (btSerial.available() > 0) {    // check if bluetooth module sends some data to esp8266
    char data = btSerial.read();     // read the data from bluetooth
    switch (data)
    {
      case 'C':                               // if receive data is 'C'(change)
        //digitalWrite(D5,!digitalRead(D5));  // 改变LED的点亮或者熄灭状态
        servoService();
        Serial.println("bluetooth chances the status");
        break;
      default:
        break;
    }
  }
}

//LED闪烁服务
void ledBlink(){
  if(ledBlinkFlag){
    digitalWrite(D3,!digitalRead(D3));// 黄灯闪烁
  }
}

//ADC服务
void adcService(){
  //输出0-1023 对应 外部输入电压 0-1.0v  
  int adcValues = analogRead(A0);
  Serial.print("ADC Value: ");  
  Serial.println(adcValues);
  if (adcValues < 300){
    ledBlinkFlag = 1;
    digitalWrite(D5,LOW);     // 绿灯熄灭
  }
}

//按键服务
void flip() {
  //PLUS_KEY
  if(KEYP==0){
    KEYP_PRESS_COUNT++;
    KEYP_UP = 1;
    if(KEYP_PRESS_COUNT<=short_press_time)
      KEYP_KEY_READ = NO_KEY_PRES;
    if(KEYP_PRESS_COUNT>=short_press_time&&KEYP_PRESS_COUNT<=long_press_time)
      KEYP_KEY_READ = PLUS_KEY_PRES;
    if(KEYP_PRESS_COUNT>=long_press_time)
      KEYP_KEY_READ = PLUS_KEY_LONG_PRES;
  }
  if(KEYP){
    KEYP_PRESS_COUNT = 0;
    KEYP_UP = 0;//按键状态，0为弹起，1为按下
  }
  //SUB_KEY
  if(KEYS==0){
    KEYS_PRESS_COUNT++;
    KEYS_UP = 1;
    if(KEYS_PRESS_COUNT<=short_press_time)
      KEYS_KEY_READ = NO_KEY_PRES;
    if(KEYS_PRESS_COUNT>=short_press_time&&KEYS_PRESS_COUNT<=long_press_time)
      KEYS_KEY_READ = SUB_KEY_PRES;
    if(KEYS_PRESS_COUNT>=long_press_time)
      KEYS_KEY_READ = SUB_KEY_LONG_PRES;
  }
  if(KEYS){
    KEYS_PRESS_COUNT = 0;
    KEYS_UP = 0;//按键状态，0为弹起，1为按下
  }


  if(KEYP_KEY_READ == PLUS_KEY_LONG_PRES && KEYP_UP == 0){
    Serial.println("--KEY_PLUS 长按 角度确定");
    ACK = 0; //确认舵机角度
    KEYP_KEY_READ = NO_KEY_PRES;
  }
  if(KEYP_KEY_READ == PLUS_KEY_PRES && KEYP_UP == 0){
    Serial.println("--KEY_PLUS 短按 角度+");
    PLUS_INIT = 1; //舵机角度调整工作
    KEYP_KEY_READ = NO_KEY_PRES;
  }
 
  if(KEYS_KEY_READ == SUB_KEY_LONG_PRES && KEYS_UP == 0){
    Serial.println("--KEY_SUB 长按 ");
    KEYS_KEY_READ = NO_KEY_PRES;
    ACK = 1;  //不确认舵机角度
    pos = 90;  //角度归中
    initServo();
  }
  if(KEYS_KEY_READ == SUB_KEY_PRES && KEYS_UP == 0){
    Serial.println("--KEY_SUB 短按 角度-");
    SUB_INIT = 1; //舵机角度调整工作
    KEYS_KEY_READ = NO_KEY_PRES;
  }
}

//舵机服务
void servoService(){
  if(switchStatus){
    switchStatus = 0;
    myServo.write(90+posChange);              // 舵机角度写入
    delay(100);
  }
  else{
    switchStatus = 1;
    myServo.write(90-posChange);              // 舵机角度写入
    delay(100);
  }
  Serial.println("开关操作完成");
  myServo.write(90);              // 舵机角度写入
  delay(100);
}

//-------------------------------  setup（配置函数） 只执行一次 ----------------------------------------
void setup() {
  initBasic();
  connectNewWifi();
}

//-------------------------------  loop（主函数）  一直执行-------------------------------------------
void loop() {
  server.handleClient();            //wifi服务
  dnsServer.processNextRequest();   //DNS服务
  bluetoothService();               //蓝牙服务
}
